#include <drivers/gpio.h>
#include <list.h>
#include <mm/mem.h>
#include <kernel/syslog.h>
#include <string.h>


/**
 * global gpio_active
 */
static struct gpio_desc *gpio_active = NULL;


/**
 * @brief set a GPIO busy status into bitmap
 * @param bit_map bit map, manager all GPIO, 1 means busy, 0 means free
 * @param pin GPIO number
 * @param pin_max max pin number
 * @param flag true means busy, false means free
 * @return none
 */
static void gpio_set_pin_busy(uint8_t *bit_map, int16_t pin, int16_t pin_max, bool flag)
{
    if(pin > pin_max) {
        GPIO_SYSLOG_ERROR("pin is invalid");
        return;
    }

    uint8_t *index_byte = (bit_map + (pin >> 3));
    uint8_t index_bit = pin & 0x07;

    if(flag) {
        *index_byte |= (1 << index_bit);
    }
    else {
        *index_byte &= ~(1 << index_bit);
    }
}


/**
 * @brief get a GPIO busy status from bitmap
 * @param bit_map: bitmap, manager all GPIO, 1 means busy, 0 means free
 * @param pin: GPIO number
 * @param pin_max: max GPIO number
 * @return 1 means busy, 0 means free, -1 means error
 */
static int gpio_get_pin_busy(uint8_t *bit_map, int16_t pin, int16_t pin_max)
{
    bool status = true;
    if(pin > pin_max) {
        GPIO_SYSLOG_ERROR("pin is invalid");
        return -1;
    }

    uint8_t index_byte = *(bit_map + (pin >> 3));
    uint8_t index_bit = pin & 0x07;

    status = index_byte & (1 << index_bit);

    return status;
}


/**
 * @brief request a GPIO
 * @param desc: GPIO description
 * @param pin: GPIO number
 * @return 0: success, -1: failed
 */
static int gpio_request_pin(struct gpio_desc *desc, int16_t pin)
{
    if(gpio_get_pin_busy(desc->map, pin, desc->npins)) {
        GPIO_SYSLOG_ERROR("pin [%d] is using, request failed", pin);
        return -1;
    }

    GPIO_DEBUG_TRACE("request %d pin successfully", pin);
    gpio_set_pin_busy(desc->map, pin, desc->npins, true);

    if(desc->ref[IO_TO_PORT(pin)] == 0 && desc->ops->set_clock) {
        desc->ops->set_clock(pin, 1);
    }

    desc->ref[IO_TO_PORT(pin)] ++;

    return 0;
}


/**
 * @brief free GPIO
 * @param desc: GPIO description
 * @param pin: GPIO pin number
 * @return none
 */
static void gpio_free_pin(struct gpio_desc *desc, int16_t pin)
{
    GPIO_DEBUG_TRACE("free %d pin successfully", pin);
    gpio_set_pin_busy(desc->map, pin, desc->npins, false);

    desc->ref[IO_TO_PORT(pin)] --;

    if(desc->ref[IO_TO_PORT(pin)] == 0 && desc->ops->set_clock) {
        desc->ops->set_clock(pin, 0);
    }
}


/**
 * @brief register a gpio description(controller)
 * @param desc: gpio description
 * @return int, 0 means success, -1 means failed 
 */
int gpio_desc_register(struct gpio_desc *desc)
{
    desc->map = kmalloc(desc->npins >> 3);
    if(desc->map == NULL) {
        GPIO_SYSLOG_ERROR("prictrl request bit map failed");
        return -1;
    }

    memset(desc->map, 0, desc->npins >> 3);

    gpio_active = desc;

    GPIO_SYSLOG_INFO("%s controller register successfully", desc->name);
    return 0;
}


/**
 * @brief request GPIO pin
 * @param pin GPIO number
 * @return 0 on success, -1 on error
 */
int gpio_request(int16_t pin)
{
    if(gpio_active == NULL) {
        GPIO_SYSLOG_ERROR("GPIO request failed: gpio controller not found\n");
        return -1;
    }

    if(pin > gpio_active->npins) {
        GPIO_SYSLOG_ERROR("GPIO request failed: pin %d is out of range\n", pin);
        return -1;
    }
    
    if(gpio_request_pin(gpio_active, pin) < 0) {
        return -1;
    }
    
    if(gpio_active->ops->init) {
        gpio_active->ops->init(pin);
    }

    return 0;
}


/**
 * @brief set a GPIO value
 * @param pin GPIO number
 * @param value GPIO value
 * @return none
 */
void gpio_set_value(int16_t pin, uint8_t value)
{
    if(gpio_active == NULL) {
        GPIO_SYSLOG_ERROR("GPIO request failed: gpio controller not found\n");
        return;
    }

    if(pin > gpio_active->npins) {
        GPIO_SYSLOG_ERROR("GPIO request failed: pin %d is out of range\n", pin);
        return;
    }
    
    GPIO_DEBUG_TRACE("set %d pin value: %d", pin, value);
    gpio_active->ops->set_value(pin, value);
}


/**
 * @brief toggle a GPIO value
 * @param pin GPIO number
 * @return none
 */
void gpio_toggle_value(int16_t pin)
{
    GPIO_DEBUG_TRACE("toggle %d pin", pin);
    gpio_active->ops->set_value(pin, !gpio_active->ops->get_value(pin));
}


/**
 * @brief get a GPIO value
 * @param pin GPIO number
 * @return GPIO value
 */
int gpio_get_value(int16_t pin)
{
    GPIO_DEBUG_TRACE("get %d pin value: %d", pin, gpio_active->ops->get_value(pin));
    return gpio_active->ops->get_value(pin);
}


/**
 * @brief Set GPIO direction to input
 * @param pin GPIO number
 * @return 0 on success, -1 on error
 */
int gpio_direction_input(int16_t pin)
{
    if(gpio_active == NULL) {
        GPIO_SYSLOG_ERROR("GPIO request failed: gpio controller not found\n");
        return -1;
    }

    if(pin > gpio_active->npins) {
        GPIO_SYSLOG_ERROR("GPIO request failed: pin number is out of range\n");
        return -1;
    }
    
    GPIO_DEBUG_TRACE("set %d pin direction input", pin);
    gpio_active->ops->set_dir(pin, false);
    return 0;
}


/**
 * @brief Set GPIO direction to output
 * @param pin GPIO pin number
 * @param value GPIO pin value
 */
int gpio_direction_output(int16_t pin, uint8_t value)
{
    if(gpio_active == NULL) {
        GPIO_SYSLOG_ERROR("GPIO request failed: gpio controller not found\n");
        return -1;
    }

    if(pin > gpio_active->npins) {
        GPIO_SYSLOG_ERROR("GPIO request failed: pin number is out of range\n");
        return -1;
    }
    
    GPIO_DEBUG_TRACE("set %d pin direction output: %d", pin, value);
    gpio_active->ops->set_dir(pin, true);
    gpio_active->ops->set_value(pin, value);
    return 0;
}


/**
 * @brief Set GPIO mux function
 * @param pin GPIO pin number
 * @param mux GPIO mux function
 * @return 0 on success, -1 on error
 */
int gpio_set_mux(int16_t pin, uint16_t mux)
{
    if(gpio_active == NULL) {
        GPIO_SYSLOG_ERROR("GPIO request failed: gpio controller not found\n");
        return -1;
    }

    if(pin > gpio_active->npins) {
        GPIO_SYSLOG_ERROR("GPIO request failed: pin number is out of range\n");
        return -1;
    }
    
    GPIO_DEBUG_TRACE("set %d pin mux: %d", pin, mux);
    gpio_active->ops->set_mux(pin, mux);

    return 0;
}


/**
 * @brief Set GPIO pull down
 * @param pin GPIO pin number
 * @param flag GPIO pull down flag, 0: disable, 1: enable
 * @return none
 */
void gpio_set_pulldown(int16_t pin, uint8_t flag)
{
    if(gpio_active == NULL) {
        GPIO_SYSLOG_ERROR("GPIO request failed: gpio controller not found\n");
        return;
    }

    if(pin > gpio_active->npins) {
        GPIO_SYSLOG_ERROR("GPIO request failed: pin number is out of range\n");
        return;
    }
    
    GPIO_DEBUG_TRACE("set %d pin pull down", pin);
    gpio_active->ops->set_pulldown(pin, flag);
}


/**
 * @brief Set GPIO pull up
 * @param pin GPIO pin number
 * @param flag GPIO pull up flag, 0: disable, 1: enable
 * @return none
 */
void gpio_set_pullup(int16_t pin, uint8_t flag)
{
    if(gpio_active == NULL) {
        GPIO_SYSLOG_ERROR("GPIO request failed: gpio controller not found\n");
        return;
    }

    if(pin > gpio_active->npins) {
        GPIO_SYSLOG_ERROR("GPIO request failed: pin number is out of range\n");
        return;
    }
    
    GPIO_DEBUG_TRACE("set %d pin pull up", pin);
    gpio_active->ops->set_pullup(pin, flag);
}


/**
 * @brief free a GPIO pin
 * @param pin GPIO pin number
 * @return none
 */
void gpio_free(int16_t pin)
{
    if(gpio_active == NULL) {
        GPIO_SYSLOG_ERROR("GPIO request failed: gpio controller not found\n");
        return;
    }

    if(pin > gpio_active->npins) {
        GPIO_SYSLOG_ERROR("GPIO request failed: pin number is out of range\n");
        return;
    }
    
    gpio_free_pin(gpio_active, pin);
}


/**
 * @brief convert GPIO pin to IRQ number
 * @param pin GPIO pin number
 * @return IRQ number
 */
int gpio_to_irq(int16_t pin, int flag)
{
    if(gpio_active == NULL) {
        GPIO_SYSLOG_ERROR("GPIO request failed: gpio controller not found\n");
        return -1;
    }

    if(pin > gpio_active->npins) {
        GPIO_SYSLOG_ERROR("GPIO request failed: pin number is out of range\n");
        return -1;
    }

    GPIO_DEBUG_TRACE("set %d pin to irq", pin);
    return gpio_active->ops->set_exti(pin, flag);
}


/**
 * @brief set multiple GPIO mux function
 * @param config GPIO mux function configuration pins
 * @param npins number of pins
 * @return 0 on success, -1 on error
 */
int gpio_mux_func(const struct gpio_conf *config, int16_t npins)
{
    for(int i = 0; i < npins; i++) {
        if(gpio_request_pin(gpio_active, config[i].pin)) {
            GPIO_SYSLOG_ERROR("pin mux function failed");
            return -1;
        }

        gpio_active->ops->set_mux(config[i].pin, config[i].mux);

        if(config[i].dir != IO_INVALID_DIR) {
            gpio_active->ops->set_dir(config[i].pin, config[i].dir);
        }

        gpio_active->ops->set_drv(config[i].pin, config[i].drv);

        if(config[i].dir != IO_INVALID_VAL) {
            gpio_active->ops->set_value(config[i].pin, config[i].val);
        }
    }

    return 0;
}


/**
 * @brief free multiple GPIO mux function
 * @param config GPIO mux function configuration pins
 * @param npins number of pins
 * @return none
 */
void gpio_mux_free(const struct gpio_conf *config, int16_t npins)
{
    for(int i = 0; i < npins; i++) {
        gpio_free_pin(gpio_active, config[i].pin);
    }
}
